/* ClientSocket.cpp
 *
 * --
 * Copyright (c) 2012, Christophe Huriaux
 * Distributed under the terms of the MIT license.
 * http://www.opensource.org/licenses/mit-license.php
 * --
 */


#include <socket/ClientSocket.h>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
#include <cerrno>
#include <debug/logger.h>

#include <iostream>
using namespace std;

ClientSocket::ClientSocket(const char* serverName, unsigned short int port)
	: Thread("I/O thread"), serverName(serverName), serverPort(port)
{

}

ClientSocket::~ClientSocket()
{
	close();
}

int
ClientSocket::resolve()
{
	memset(&serverSockAddr,	0, sizeof(struct sockaddr_in));

	// Resolving hostname if serverName is not a valid IP
	if (inet_aton(serverName.c_str(), &serverSockAddr.sin_addr) == 0)
	{
		serverHostent = gethostbyname(serverName.c_str());
		// cannot retrive the according IP
		if (serverHostent == NULL)
			return -1;

		serverSockAddr.sin_addr = *(struct in_addr*)serverHostent->h_addr;
	}

	serverSockAddr.sin_port = htons(serverPort);
	serverSockAddr.sin_family = AF_INET;

	serverNameResolved = inet_ntoa(serverSockAddr.sin_addr);

	return 0;
}

int
ClientSocket::open()
{
	int r;
	close();

	if (pthread_mutex_init(&ioMutex, NULL) != 0)
		return -1;

	sock = socket(AF_INET, SOCK_STREAM, 0);

	if (sock < 0)
		return -1;

	r = connect(sock, (struct sockaddr *)&serverSockAddr,
			sizeof(struct sockaddr));

	if (r < 0)
		return -1;

	setNonBlock(true);
#if CLIENTSOCKET_DEBUG
	cout << "Running I/O thread..." << endl;
#endif

	recvBufferBegin = 0;
	recvBufferNext = 0;
	recvBufferEmpty = true;
	recvBufferFull = false;
	return start();
}

int
ClientSocket::close()
{
	int r = 0;

	// Properly terminate the IO thread
	if (isAlive())
	{
#if CLIENTSOCKET_DEBUG
		cout << "Waiting for I/O thread to terminate..." << endl;
#endif
		stop();
		if (join() != 0)
		{
#if CLIENTSOCKET_DEBUG
			cout << "pthread_join error." << endl;
#endif
			r = -1;
		}
	}

	// Stop I/O on the socket
	if (shutdown(sock, SHUT_RDWR) != 0)
		r = -1;

	// Close the socket
	if (::close(sock) != 0)
		r = -1;

	return r;
}


/*
 * Thread management
 */

void*
ClientSocket::run()
{
	ssize_t readCount;
	size_t maxCount;
	char readBuffer[128];


#if CLIENTSOCKET_DEBUG
		cout << "I/O thread begin" << endl;
#endif
	while (!shouldStop())
	{
		if (!recvBufferFull)
		{
			maxCount = remaining();
			if (maxCount > sizeof(readBuffer))
				maxCount = sizeof(readBuffer);

#if CLIENTSOCKET_DEBUG
		cout << "I/O thread read" << endl;
#endif
			readCount = ::read(sock, readBuffer, maxCount);
#if CLIENTSOCKET_DEBUG
		cout << "I/O thread readd " << dec << readCount << " bytes." << endl;
#endif

			if (readCount > 0)
			{
				pthread_mutex_lock(&ioMutex);
#if CLIENTSOCKET_DEBUG
		cout << "I/O thread read " << readCount << " bytes" << endl;
#endif
				if ((recvBufferNext < recvBufferBegin)
				 || ((recvBufferNext >= recvBufferBegin)
				  && ((ssize_t)(CLIENTSOCKET_IO_BUFFER_SIZE-recvBufferNext) >= readCount)))
				{
					memcpy(&recvBuffer[recvBufferNext], readBuffer, readCount);
				}
				else if (recvBufferNext >= recvBufferBegin)
				{
					int split = (CLIENTSOCKET_IO_BUFFER_SIZE-recvBufferNext);

					memcpy(&recvBuffer[recvBufferNext], readBuffer, split);
					memcpy(recvBuffer, readBuffer+split, readCount-split);
				}

				recvBufferNext += readCount;
				if (recvBufferNext >= CLIENTSOCKET_IO_BUFFER_SIZE)
					recvBufferNext -= CLIENTSOCKET_IO_BUFFER_SIZE;

				recvBufferEmpty = false;
				if (recvBufferNext == recvBufferBegin)
				{
					logg(warning) << "input buffer is full (size="
						<< CLIENTSOCKET_IO_BUFFER_SIZE << ")" << logg::endl;
					recvBufferFull = true;
				}
				pthread_mutex_unlock(&ioMutex);
			}
			else
			{
				if (readCount == -1)
				{
					if (errno == EWOULDBLOCK || errno == EAGAIN)
						sleep(1);
					else
					{
						logg(critical) << "I/O error : " << strerror(errno) << logg::endl;
						pthread_exit(NULL);
					}
				}
				else
				{
					logg(warning) << "Disconnected from server." << logg::endl;
				}
			}
		}
		else // recv buffer full
		{
#if CLIENTSOCKET_DEBUG
		cout << "I/O recv buffer full" << endl;
#endif
			usleep(10000);
		}
	}

#if CLIENTSOCKET_DEBUG
		cout << "I/O thread end" << endl;
#endif

	return NULL;
}

void
ClientSocket::atCleanup()
{
	Thread::atCleanup();
	pthread_mutex_unlock(&ioMutex);
}

/*
 * I/O
 */

int
ClientSocket::count()
{
	int c = 0;

	pthread_mutex_lock(&ioMutex);
	if (recvBufferBegin < recvBufferNext)
	{
		c = recvBufferNext - recvBufferBegin;
	}
	else if (recvBufferBegin > recvBufferNext)
	{
		c = (CLIENTSOCKET_IO_BUFFER_SIZE - recvBufferBegin)
				+ recvBufferNext;
	}
	else	// ioBufferBegin == ioBufferEnd
	{
		if (recvBufferFull)
			c = CLIENTSOCKET_IO_BUFFER_SIZE;
	}

	pthread_mutex_unlock(&ioMutex);
	return c;
}

int
ClientSocket::remaining()
{
	return CLIENTSOCKET_IO_BUFFER_SIZE-count();
}

std::string
ClientSocket::read(int length)
{
	pthread_mutex_lock(&ioMutex);
	pthread_mutex_unlock(&ioMutex);
	return std::string("");
}


ssize_t
ClientSocket::read(void* buffer, ssize_t count)
{
	if (count > this->count())
		return 0;

	pthread_mutex_lock(&ioMutex);

	if (count > 0)
		recvBufferFull = false;

	if (recvBufferBegin + count <= CLIENTSOCKET_IO_BUFFER_SIZE)
	{
		memcpy(buffer, &recvBuffer[recvBufferBegin], count);
		recvBufferBegin += count;

		if (recvBufferBegin == CLIENTSOCKET_IO_BUFFER_SIZE)
			recvBufferBegin = 0;
	}
	else
	{
		ssize_t split = (CLIENTSOCKET_IO_BUFFER_SIZE - recvBufferBegin);

		memcpy(buffer, &recvBuffer[recvBufferBegin], split);
		memcpy((char*)buffer+split, recvBuffer, count-split);
		recvBufferBegin = count-split;
	}

	pthread_mutex_unlock(&ioMutex);
	return count;
}

int
ClientSocket::write(const void* buffer, size_t count)
{
	int rc = ::write(sock, buffer, count);

#if CLIENTSOCKET_DEBUG
	cout << "Writing " << count << " bytes on socket. " << rc << " written." << endl;
	cout << "Content:" << endl;

	for (unsigned int i = 0; i < count; i++)
		cout << hex << (int)(((char*)buffer)[i]) << " ";

	cout << dec << endl;
#endif
	return rc;
}


/*
 * Getters and setters
 */

int
ClientSocket::setNonBlock(bool nonblock)
{
	int sockFlags = fcntl(sock, F_GETFL, 0);

	if (sockFlags == -1)
		sockFlags = 0;

	if (nonblock)
		return fcntl(sock, F_SETFL, sockFlags | O_NONBLOCK);
	else
		return fcntl(sock, F_SETFL, sockFlags & (~O_NONBLOCK));
}

const char*
ClientSocket::getServerName()
{
	return serverName.c_str();
}

void
ClientSocket::setServerName(const char* serverName)
{
	this->serverName = std::string(serverName);
}


const char*
ClientSocket::getResolvedServerName()
{
	return serverNameResolved.c_str();
}
